<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    /*
    WeakMap => {
        target1 => {
            key1: set{effect1, effect2, ....}
            key2: set{effect1, effect2, ....}
        },
        target2 => {
            key1: set{effect1, effect2, ....}
        }
    }
    */

    /*
    obj => {
      ok => [fn],
      text => [fn]
    }
    */
    const data = {
      ok: true,
      text: 'hello world'
    }

    const bucket = new WeakMap()

    function cleanup(effectFn) {
      for (let i = 0; i < effectFn.deps.length; i++) {
        effectFn.deps[i].delete(effectFn)
      }
      effectFn.deps.length = 0
    }

    let activeEffect
    const effect = (fn) => {
      const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        fn()
      }

      effectFn.deps = []
      effectFn()
    }

    function track(target, key) {
      let depsMap = bucket.get(target)
      if (!depsMap) {
        bucket.set(target, (depsMap = new Map()))
      }

      let deps = depsMap.get(key)
      if (!deps) {
        depsMap.set(key, (deps = new Set()))
      }
      deps.add(activeEffect)
      activeEffect.deps.push(deps)
    }

    function trigger(target, key) {
      const depsMap = bucket.get(target)
      if (!depsMap) return
      const effects = depsMap.get(key)
      const effectsToRun = new Set(effects)
      effectsToRun && effectsToRun.forEach(fn => fn())
    }

    const obj = new Proxy(data, {
      get(target, key) {
        // 读取key值的时候，要给key，添加一个响应方法
        track(target, key)
        return target[key]
      },
      set(target, key, newVal) {
        // 修改key值的时候，调用响应方法
        target[key] = newVal
        trigger(target, key)
        return true
      }
    })

    effect(() => {
      console.log('effect run');
      document.body.innerText = obj.ok ? obj.text : 'hello vue3'
    })

    setTimeout(() => {
      obj.ok = false
    }, 1000)

    setTimeout(() => {
      obj.text = 'hello'
      obj.text = 'hello'
    }, 1000)

  </script>
</body>

</html>